Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert

Bereits in anderen scripten wurde vorbereitet:

Die Daten werden wiefolgt vorbereitet:

TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)

Laden der Daten

sampled_buildings = read_rds('output/sampled_buildings.rds')
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields

Schulwege

route_matrix = read_rds('output/route_matrix.rds')

Schulkapazitäten und Einwohnerzahler auf LOR-Ebene

kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
  Bezirk = col_character(),
  Schulnummer = col_character(),
  Schulname = col_character(),
  Plätze = col_integer(),
  Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
  .default = col_character(),
  ZEIT = col_integer(),
  STADTRAUM = col_integer(),
  E_E = col_number(),
  E_EM = col_number(),
  E_EW = col_number(),
  E_E50_55 = col_number(),
  E_E25U55 = col_number(),
  E_E55U65 = col_number(),
  E_E65U80 = col_number()
)
See spec(...) for full column specifications.

Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten

re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2)
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
  group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"

Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?

re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"

Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:

bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
In Anmeldeliste, fehlt in Schulstand
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
In re_schulstand, fehlt in Anmeldeliste
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))

Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.

data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
    coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):

relevant_schools = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37

Mapping Bezirk->LOR->Block

df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)

Sanity-Check: LORs und Blöcke im Bezirk

bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk

ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Alter auf Blockebene

Die Struktur der Datei in CSV-Format ist: BLK, 0,1,2,3,4,5,6,7,8,9,10,11,Gesamtergebnis.

einwohner_blk = readOGR('download/03_2_BLK_Einw_Dez2015_Alter.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/03_2_BLK_Einw_Dez2015_Alter.geojson", layer: "OGRGeoJSON"
with 1165 features
It has 14 fields
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>%
  left_join(
    einwohner_blk@data %>%
      set_names(strsplit("BLK,0,1,2,3,4,5,6,7,8,9,10,11,Gesamt", ",")[[1]]) %>%
      gather(age, num, -BLK, -Gesamt)
    , by=c('id'='BLK')
    ) %>%
  filter(!is.na(age)) %>%
  mutate(num=ifelse(num == 0, NA, num), age=factor(as.character(age), levels=as.character(0:11)))

ggmap(map) +
  geom_polygon(aes(x=long, y=lat, group=group, fill=num), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  facet_wrap(~ age, ncol = 6)

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>%
  left_join(
    einwohner_blk@data %>% set_names(strsplit("BLK,0,1,2,3,4,5,6,7,8,9,10,11,Gesamt", ",")[[1]]), by=c('id'='BLK')) %>%
  mutate(kids=(`5`+`6`)/2, kids=ifelse(kids == 0, NA, kids))

data$panel = "5&6"

data2 = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>%
  left_join(
    einwohner_blk@data %>% set_names(strsplit("BLK,0,1,2,3,4,5,6,7,8,9,10,11,Gesamt", ",")[[1]]), by=c('id'='BLK')) %>%
  mutate(kids=(`1`+`2`+`3`+`4`+`5`+`6`)/6, kids=ifelse(kids == 0, NA, kids))

data2$panel = "1-6"

data = rbind(data, data2)

ggmap(map) +
  geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  facet_wrap(~ panel)

Berechnung der Population der Blöcke, Strukturquote

Strukturquote = 0.9

kids_in_blks = einwohner_blk@data %>%
  set_names(strsplit("BLK,0,1,2,3,4,5,6,7,8,9,10,11,Gesamt", ",")[[1]]) %>%
  mutate(kids=(`1`+`2`+`3`+`4`+`5`+`6`)/6*Strukturquote) # FIXME only 5 and 6?

row.names(kids_in_blks) = kids_in_blks$BLK

Verfügbare Plätze

relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()

Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken

'Summe Kapas'
[1] "Summe Kapas"
Summe Kapas
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
Anmeldungen
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
Kids laut Statistik
kids_in_blks$kids %>% sum
[1] 2539.95

Schulwege von Blöcken zu Schulen aggregieren

Für jedes Wohngebäude suchen wir den zugehörigen Block

residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
  inner_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)

Plot der relevanten Blöcke (mit Wohngebäuden) und Population

Welche Blöcke haben Routen( weil Wohngebäude) und eine Population (weil Kinder zwischen 1 und 6)?

polys = tidy(blk[blk$BEZ == bez_id,], region='BLK')
routedata = polys %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
popdata = polys %>% inner_join(kids_in_blks %>% filter(kids > 0), by=c('id'='BLK'))

ggmap(map) +
  geom_polygon(aes(x=long, y=lat, group=group), fill='blue', data=popdata) +
  geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=routedata) +
  coord_map(xlim=c(min(routedata$long)-0.01, max(routedata$long)+0.01), ylim=c(min(routedata$lat)-0.01, max(routedata$lat)+0.01))

ggmap(map) +
  geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=routedata) +
  geom_polygon(aes(x=long, y=lat, group=group), fill='blue', data=popdata) +
  coord_map(xlim=c(min(routedata$long)-0.01, max(routedata$long)+0.01), ylim=c(min(routedata$lat)-0.01, max(routedata$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks

Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
  geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1043   33
travel_matrix

Sozioökonomische Daten

Wir haben Sozioökonomische Daten in den Wahlbezirken. Strategie: - Schneiden der Wahlbezirke mit den Blöcken - Übernahme des Prozentwertes vom Wahlbezirk für jeden (Unter-)block - Zuordnung zu jedem Block und Vereinigung durch Flächen/Wohnhaus-gewichtetes Mittel des Prozentwertes

UWB = readOGR('download/RBS_OD_UWB_AGH2016.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_UWB_AGH2016.geojson", layer: "OGRGeoJSON"
with 1779 features
It has 4 fields
UWB$ID = paste0(UWB$BEZ, UWB$UWB)
sozio_UWB = read_excel('download/DL_BE_AH2016_Strukturdaten.xlsx', sheet = 3) %>% select(ID, sgbIIu65=`Einwohner unter 65 in SGB II 2014 Prozent`)
UWB = UWB %>% sp::merge(sozio_UWB, by='ID')

ggplot(broom::tidy(UWB, region='ID') %>% inner_join(UWB %>% as.data.frame, by=c('id'='ID'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Check for self intersections

wgs84 = CRS(proj4string(blk))
ea_projection = CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
blk_in_bez = blk[blk$BEZ == bez_id,]
gIsValid(blk_in_bez)
[1] TRUE
blk_in_bez = gBuffer(spTransform(blk_in_bez, ea_projection), byid=T, width = -0.1)
any(xor(gIntersects(blk_in_bez, byid=T, returnDense = T), diag(1, length(blk_in_bez)) == 1))
[1] FALSE
plot(blk_in_bez)

#gIntersects(UWB[UWB$BEZ == '07',], byid=TRUE)
uwb_in_bez = gBuffer(spTransform(UWB[UWB$BEZ == '07',], ea_projection), byid=T, width=-0.1)
gIsValid(uwb_in_bez)
[1] TRUE
any(xor(gIntersects(uwb_in_bez, byid=T, returnDense = T), diag(1, length(uwb_in_bez)) == 1))
[1] FALSE
plot(uwb_in_bez)

intersection = gIntersection(uwb_in_bez, blk_in_bez, byid = T, drop_lower_td = T)

gIsValid(intersection)
[1] TRUE
plot(intersection)

intersection_uwb_data = intersection %>% over(uwb_in_bez)
intersection_blk_data = intersection %>% over(blk_in_bez)
intersection_area = gArea(intersection, byid=T)
intersection_data = cbind(
    intersection_uwb_data %>% select(UWB_ID=ID, sgbIIu65),
    intersection_blk_data %>% select(BLK, BLK_Einw=Einw),
    data.frame(area=intersection_area) # FIXME how else to normalize?
    )
length(intersection)
[1] 1219
nrow(blk_in_bez)
[1] 1201
# did we miss any blocks?
setdiff(blk_in_bez$BLK, intersection_data$BLK)
character(0)

Pro Block mische die SGB-Werte der Unterblöcke gewichtet nach Fläche. FIXME - besser nach Anzahl der Wohnhäuser?

sgbII_blk = intersection_data %>%
  group_by(BLK) %>%
  summarise(sgbIIu65=sum(sgbIIu65*area)/sum(area)/100)
ggplot(broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK') %>% left_join(sgbII_blk, by=c('id'='BLK'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Adjazenz von Blöcken

row.names(blk_in_bez) = blk_in_bez$BLK
buffeded_blk_in_bez = gBuffer(blk_in_bez, byid=T, width=40)
adjacency = gIntersects(gBuffer(blk_in_bez, byid=T, width=40), byid = T)
rownames(adjacency) = blk_in_bez$BLK
colnames(adjacency) = blk_in_bez$BLK

adjacency_df = adjacency %>% as.data.frame() %>% mutate(from=rownames(.)) %>%
  gather(to, connected, -from) %>% filter(connected) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(from_long=V1, from_lat=V2) %>% mutate(from=rownames(.))) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(to_long=V1, to_lat=V2) %>% mutate(to=rownames(.)))
Joining, by = "from"
Joining, by = "to"
ggplot() +
  geom_polygon(aes(x=long, y=lat, group=group), fill='gray', data=broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK')) + 
  geom_segment(aes(x=from_long, y=from_lat, xend=to_long, yend=to_lat), size=0.1, color='black', data=adjacency_df) +
  theme_nothing() + coord_map()
Warning: `panel.margin` is deprecated. Please use `panel.spacing` property
instead

ggsave('figs/adjacency.pdf')
Saving 7 x 5 in image
adjacency_df %>% filter(connected & from != to) %>% select(from, to) %>% write_csv('app/data/adjacency.csv')

Relevante Daten auswählen

optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids)
nrow(optim_kids_in_blks)
[1] 966
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)

optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]

dim(optim_matrix)
[1] 966  32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2538.15

Naive (initiale) Zuordnung: Jeder Block zur nächsten Schule

solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup

optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 736677.2
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
  geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
  geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  guides(color=F, fill=F)

Darstellung der Zuordnung als Tabelle

library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
  group_by(school) %>% summarise(
    kids=sum(kids),
    num_blocks=n(),
    min_dist=min(min),
    avg_dist=mean((kids*avg)/sum(kids)),
    max_dist=max(max)
  ) %>%
  inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
  mutate(
    utilization=kids/Kapa
  ) %>% select(
   Schule=school,
   `Blöcke`=num_blocks,
   Kapazität=Kapa,
   Kinder=kids,
   Auslastung=utilization,
   `Weg (min)`=min_dist,
   `Weg (Ø)`=avg_dist,
   `Weg (max)`=max_dist
  ) %>%
  formattable(
    list(
      Kinder = formatter("span", x ~ digits(x, 2)),
      Auslastung = formatter("span",
        style = x ~ style(color = ifelse(x < 1, "green", "red")),
        x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
      ),
      `Weg (Ø)` = proportion_bar("lightblue"),
      `Weg (min)` = proportion_bar("lightblue"),
      `Weg (max)` = proportion_bar("lightblue")
    )
  )
Schule Blöcke Kapazität Kinder Auslastung Weg (min) Weg (Ø) Weg (max)
07G01 15 66 85.05 128.86% 181.547440 700.0492 1346.5031
07G02 19 98 45.30 46.22% 105.499832 623.1903 1108.7021
07G03 17 73 54.45 74.59% 15.600883 367.5294 845.7204
07G05 19 78 94.50 121.15% 85.897629 601.2110 986.3845
07G06 27 55 84.75 154.09% 14.026745 797.5172 1234.1342
07G07 11 81 27.60 34.07% 43.258247 690.1380 1806.2324
07G10 26 112 146.55 130.85% 61.768867 607.5237 1464.8206
07G12 12 75 31.20 41.60% 130.699646 411.4655 674.6335
07G13 24 82 113.85 138.84% 25.176670 450.8940 1067.4244
07G14 31 78 70.35 90.19% 16.395737 402.8689 768.4520
07G15 51 104 184.20 177.12% 3.375702 808.5565 1474.6599
07G16 22 104 58.65 56.39% 22.915071 496.8528 944.5726
07G17 22 100 57.00 57.00% 88.050331 388.6880 695.9189
07G18 18 44 64.35 146.25% 2.263283 354.9368 662.2927
07G19 34 112 126.60 113.04% 2.990739 1094.2487 2207.5225
07G20 42 81 139.50 172.22% 110.340210 696.1919 1704.6410
07G21 24 72 79.20 110.00% 222.134750 545.1009 1511.8127
07G22 25 91 54.15 59.51% 3.750214 569.9378 1353.2026
07G23 8 50 15.75 31.50% 65.594955 708.3703 1333.5781
07G24 27 75 73.35 97.80% 92.837883 641.5522 1421.1660
07G25 37 75 141.75 189.00% 92.507385 652.2811 1237.5529
07G26 20 52 18.00 34.62% 57.143013 524.2916 896.1053
07G27 16 75 46.65 62.20% 95.367569 696.7090 1357.3337
07G28 53 75 78.60 104.80% 134.617737 1025.2054 2809.9302
07G29 93 104 104.25 100.24% 123.937210 928.5043 2608.2744
07G30 38 72 53.40 74.17% 101.384521 810.4917 2524.4463
07G31 18 78 46.80 60.00% 198.014374 871.0089 1560.6522
07G32 56 78 55.05 70.58% 7.506210 755.1895 1366.1006
07G34 32 100 155.40 155.40% 0.000000 1092.8906 2080.1841
07G35 34 75 90.00 120.00% 160.449890 789.4181 1531.3287
07G36 40 100 58.80 58.80% 144.836456 920.1215 2250.3574
07G37 55 69 83.10 120.43% 142.399994 1196.1082 2097.5693

ndH und LMB-Daten (nicht offen)

ndh_lmb = read_excel('download/LMB_ndH2015.xlsx') %>% select(
  entity_id=BSN,
  ndH=`Anteil ndH`,
  LMB=`Anteil LMB`
)
ndh_lmb

Daten für die App speichern

Neue Daten

Entities / Schulen

entities = subset(re_schulstand_df, spatial_name %in% relevant_schools) %>%
  rename(entity_id = spatial_name) %>%
  select(-gml_id, -spatial_alias, -spatial_type) %>%
  inner_join(rename(relevant_kapas, capacity=Kapa), by=c('entity_id'='Schulnummer')) %>%
  left_join(ndh_lmb, by=c('entity_id'))
coordinates(entities) = ~ lon + lat
proj4string(entities) = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
file.remove('app/data/entities.geojson')
[1] TRUE
entities %>% writeOGR('app/data/entities.geojson', layer="entities", driver="GeoJSON", check_exists=F)
entities@data %>% select(entity_id, capacity) %>% write_csv('app/data/entities.csv')

Units / Statistische Blöcke

units = subset(blk, BEZ == bez_id)

units@data$PLR = NULL
#units@data$Einw = NULL
units@data$BEZ = NULL
units@data$unit_id = units@data$BLK
units@data$BLK = NULL

units = units %>% sp::merge(sgbII_blk %>% rename(unit_id=BLK)) %>%
  sp::merge(optim_kids_in_blks %>% select(unit_id=BLK, population=kids))

file.remove('app/data/units.geojson')
[1] TRUE
units %>% writeOGR('app/data/units.geojson', layer="entities", driver="GeoJSON", check_exists=F)
units@data %>% write_csv('app/data/units.csv')

Zuordnung

assignment = solution %>% select(unit_id=BLK, entity_id=school)
assignment %>% write_csv('app/data/assignment.csv')

Weights / Schulwege

travel_from_blks %>%
  rename(unit_id=BLK, entity_id=dst) %>%
  #gather(weight, value, -unit_id, -entity_id) %>%
  write_csv('app/data/weights.csv')

Zusätzliche Daten

file.copy('download/RBS_OD_BEZ_2015_12.geojson', 'app/data/RBS_OD_BEZ_2015_12.geojson')
[1] FALSE
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgbGlicywgaW5jbHVkZT1GLCB3YXJuaW5nPUZ9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkobWFwdG9vbHMpCmxpYnJhcnkocmdlb3MpCmBgYAoKRGllc2VzIE5vdGVib29rIGJlcmVpdGV0IGRpZSBEYXRlbiBmw7xyIGRpZSBJbnRlbGxpZ2VudCBab25pbmcgRW5naW5lIHZvci4gRXMgc3BlaWNoZXJ0CgotIGVudGl0aWVzLmdlb2pzb24g4oCUIFNjaHVsZW4sIGRlcmVuIEdlb2tvb3JkaW5hdGVuIHVuZCBBdHRyaWJ1dGU6IGVudGl0eV9pZCwgY2FwYWNpdHksIHVuZCBhbmRlcmUgQXR0cmlidXRlCi0gZW50aXRpZXMuY3N2IOKAlCBTdGF0aXN0aXNjaGUgQmzDtmNrZSBCZXJsaW5zIHVuZCBvcHRpbWllcnVuZ3NyZWxldmFudGUgQXR0cmlidXRlOiBlbnRpdHlfaWQsIGNhcGFjaXR5Ci0gdW5pdHMuZ2VvanNvbiDigJQgU3RhdGlzdGlzY2hlIEJsw7Zja2UgQmVybGlucywgZGVyZW4gR2VvbWV0cmllIHVuZCBBdHRyaWJ1dGUKLSB1bml0cy5jc3Yg4oCUIFN0YXRzaXRpc2NoZSBCbMO2Y2tlIEJlcmxpbnMgdW5kIG9wdGltaWVydW5nc3JlbGV2YW50ZSBBdHRyaWJ1dGU6IHVuaXRfaWQsIHBvcHVsYXRpb24sIHBlcmNlbnRhZ2Vfc2diCi0gd2VpZ2h0cy5jc3Yg4oCUIG9wdGltaWVydW5nc3JlbGV2YW50ZSBHZXdpY2h0ZSB3aWUgRnXDn3dlZ2UgLyBTcGFsdGVuOiBlbnRpdHlfaWQsIHVuaXRfaWQsIHdlaWdodCwgdmFsdWUKLSBhc3NpZ25tZW50LmNzdiDigJQgZWluZSBpbml0aWFsZSBadW9yZG51bmcgLyBTcGFsdGVuOiB1bml0X2lkLCBlbnRpdHlfaWQKCkJlcmVpdHMgaW4gYW5kZXJlbiBzY3JpcHRlbiB3dXJkZSB2b3JiZXJlaXRldDoKCi0gRnXDn3dlZ2VuIHZvbiBlaW5lciBncm/Dn2VuIFNpY2hwcm9iZSB2b24gKF9Xb2huXy0pR2Viw6R1ZGVuIHp1IGFsbGVuIFNjaHVsZW4gd3VyZGVuIGJlcmVjaG5ldCB1bmQgaW4gYHJvdXRlX21hdHJpeC5jc3ZgIGdlc3BlaWNoZXJ0Ci0gRGllIFN0aWNocHJvYmUgd3VyZGUgaW4gYHNhbXBsZWRfYnVpbGRpbmdzLmNzdmAgZ2VzcGVpY2hlcnQKCkRpZSBEYXRlbiB3ZXJkZW4gd2llZm9sZ3Qgdm9yYmVyZWl0ZXQ6CgotIHBybyBCbG9jayB3ZXJkZW4gZGllIEFuemFobCBkZXIgZWluenVzY2h1bGVuZGVuIEtpbmRlciBtaXQgSGlsZmUgZGVyIEVpbndvaG5lcnphaGxlbiBuYWNoIEFsdGVyIGF1ZiBMT1ItRWJlbmUgaW4gYEVXUjIwMTUxMkVfTWF0cml4LmNzdmAgaG9jaGdlcmVjaG5ldAogICAgLSBLaW5kZXIgZGVzIExPUiB3ZXJkZW4gQW50ZWlsaWcgbmFjaCBFaW53b2huZXJ6YWhsIGRlcyBCbG9ja3MgaW0gVmVyaMOkbHRuaXMgenVtIExPUiBhdWYgZGllIEJsw7Zja2UgdmVydGVpbHQKLSBlcyB3ZXJkZW4gbWluaW1hbGUsIGR1cmNoc2Nobml0dGxpY2hlIHVuZCBtYXhpbWFsZSBGdcOfd2VnZSBhdXMgamVkZW0gQmxvY2sgZXJyZWNobmV0CgpUT0RPOgotIGRpZSBzb3ppb8O2a29ub21pc2NoZW4gRmFrdG9yZW4gd2VyZGVuIGF1cyBkZW4gV2FobGJlemlya2VuIGF1ZiBkaWUgQmzDtmNrZSBob2NoZ2VyZWNobmV0IChodHRwczovL2dpdGh1Yi5jb20vYmVybGluZXJtb3JnZW5wb3N0L2NvZ3JhbikKCiMjIExhZGVuIGRlciBEYXRlbgoKYGBge3IgbG9hZCBkYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzYW1wbGVkX2J1aWxkaW5ncyA9IHJlYWRfcmRzKCdvdXRwdXQvc2FtcGxlZF9idWlsZGluZ3MucmRzJykKYmV6ID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpibGsgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmxvciA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9MT1JfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKcmVfc2NodWxzdGFuZCA9IHJlYWRPR1IoJ2Rvd25sb2FkL3JlX3NjaHVsc3RhbmQuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKIyMjIFNjaHVsd2VnZQoKYGBge3J9CnJvdXRlX21hdHJpeCA9IHJlYWRfcmRzKCdvdXRwdXQvcm91dGVfbWF0cml4LnJkcycpCmBgYAoKIyMjIFNjaHVsa2FwYXppdMOkdGVuIHVuZCBFaW53b2huZXJ6YWhsZXIgYXVmIExPUi1FYmVuZQoKYGBge3J9CmthcGFzID0gcmVhZF9jc3YoJ2Rvd25sb2FkL2FubWVsZGV6YWhsZW4uY3N2JykgJT4lIGZpbHRlcihncmVwbCgnRycsIFNjaHVsbnVtbWVyKSkgJT4lIGZpbHRlcighaXMubmEoYFBsw6R0emVgKSkKZWlud29obmVyX2xvciA9IHJlYWRfZGVsaW0oJ2Rvd25sb2FkL0VXUjIwMTUxMkVfTWF0cml4LmNzdicsIGRlbGltPSc7JykKYGBgCgojIyDDnGJlcnByw7xmdW5nIGRlciBWb2xsc3TDpG5kaWdrZWl0IGRlciBEYXRlbiDDvGJlciBBbm1lbGRlemFobGVuL0thcGF6aXTDpHRlbgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZShsb249Y29vcmRzLngxLCBsYXQ9Y29vcmRzLngyKQpyZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgbXV0YXRlKEJFWklSSz1lbmMydXRmOChhcy5jaGFyYWN0ZXIoQkVaSVJLKSkpICU+JQogIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKQpgYGAKCkbDvHIgd2VsY2hlIEJlemlya2UgaGFiZW4gd2lyIGbDvHIgYWxsZSBTY2h1bGVuIEthcGF6aXTDpHRlbiBnZWdlYmVuPwoKYGBge3J9CnJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUgZ3JvdXBfYnkoQkVaSVJLKSAlPiUgc3VtbWFyaXNlKGBBbnphaGwgU2NodWxlbmAgPSBuKCkpICU+JQogIHJlbmFtZShCZXppcms9QkVaSVJLKSAlPiUgbGVmdF9qb2luKGthcGFzICU+JSBncm91cF9ieShCZXppcmspICU+JSBzdW1tYXJpc2UoYE1pdCBLYXBheml0w6R0YCA9IG4oKSkpICU+JSBmaWx0ZXIoYEFuemFobCBTY2h1bGVuYCA9PSBgTWl0IEthcGF6aXTDpHRgKQpgYGAKCsOcYmVycHLDvGZ1bmcgb2IgZGllIExpc3RlIGRlciBTY2h1bGVuIHVuZCBMaXN0ZSBkZXIgU2NodWxlbiBtaXQgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIGdsZWljaCBzaW5kOgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKCmBgYHtyfQptYXAgPSBnZXRfbWFwKCdCZXJsaW4nKQpgYGAKCgpgYGB7cn0KcmVfc2NodWxzdGFuZF9kZl93X2thcGFzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgbGVmdF9qb2luKGthcGFzLCBieT1jKCdzcGF0aWFsX25hbWUnPSdTY2h1bG51bW1lcicpKQpgYGAKClBsb3QgYWxsZXIgU2NodWxlbiwgbWl0IGRlciBJbmZvLCBvYiBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gdmVyZsO8Z2JhciBzaW5kLgoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmspICU+JSBtdXRhdGUobWlzc2luZy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9bWlzc2luZy5jYXBhKSwgZGF0YT1kYXRhKSArCiAgICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbiktMC4wMSwgbWF4KGRhdGEkbG9uKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgpGaWx0ZXIgYXVmIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiAoZsO8ciBULVMgc2luZCBkYXMgYWxsZSk6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGZfd19rYXBhcyAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIGZpbHRlcihCRVpJUks9PWJlemlyayAmICFpcy5uYShgUGzDpHR6ZWApKSAlPiUgLiRzcGF0aWFsX25hbWUKcmVsZXZhbnRfc2Nob29scwpgYGAKCiMjIE1hcHBpbmcgQmV6aXJrLT5MT1ItPkJsb2NrCgpgYGB7cn0KZGZfYmV6ID0gYXMuZGF0YS5mcmFtZShiZXopCmRmX2xvciA9IGFzLmRhdGEuZnJhbWUobG9yKQpkZl9ibGsgPSBhcy5kYXRhLmZyYW1lKGJsaykKYGBgCgojIyMgU2FuaXR5LUNoZWNrOiBMT1JzIHVuZCBCbMO2Y2tlIGltIEJlemlyawoKYGBge3J9CmJlel9pZCA9IGZpbHRlcihkZl9iZXosIEJFWk5BTUUgPT0gYmV6aXJrKSRCRVoKcmVsZXZhbnRfbG9ycyA9IGRmX2xvciAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlbGV2YW50X2Jsa3MgPSBkZl9ibGsgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWxvcltsb3IkQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6LCBjb2xvcj0ncmVkJykKYGBgCgojIyMgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJsa1tibGskQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6W2JleiRCRVogPT0gYmV6X2lkLF0sIGNvbG9yPSdyZWQnKQpgYGAKCiMjIEFsdGVyIGF1ZiBCbG9ja2ViZW5lCgpEaWUgU3RydWt0dXIgZGVyIERhdGVpIGluIENTVi1Gb3JtYXQgaXN0OiBCTEssIDAsMSwyLDMsNCw1LDYsNyw4LDksMTAsMTEsR2VzYW10ZXJnZWJuaXMuCgpgYGB7cn0KZWlud29obmVyX2JsayA9IHJlYWRPR1IoJ2Rvd25sb2FkLzAzXzJfQkxLX0VpbndfRGV6MjAxNV9BbHRlci5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYGBgCgpgYGB7cn0KZGF0YSA9IHRpZHkoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nQkxLJykgJT4lCiAgbGVmdF9qb2luKAogICAgZWlud29obmVyX2Jsa0BkYXRhICU+JQogICAgICBzZXRfbmFtZXMoc3Ryc3BsaXQoIkJMSywwLDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExLEdlc2FtdCIsICIsIilbWzFdXSkgJT4lCiAgICAgIGdhdGhlcihhZ2UsIG51bSwgLUJMSywgLUdlc2FtdCkKICAgICwgYnk9YygnaWQnPSdCTEsnKQogICAgKSAlPiUKICBmaWx0ZXIoIWlzLm5hKGFnZSkpICU+JQogIG11dGF0ZShudW09aWZlbHNlKG51bSA9PSAwLCBOQSwgbnVtKSwgYWdlPWZhY3Rvcihhcy5jaGFyYWN0ZXIoYWdlKSwgbGV2ZWxzPWFzLmNoYXJhY3RlcigwOjExKSkpCgpnZ21hcChtYXApICsKICBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPW51bSksIGRhdGE9ZGF0YSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpICsKICBmYWNldF93cmFwKH4gYWdlLCBuY29sID0gNikKYGBgCgoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JQogIGxlZnRfam9pbigKICAgIGVpbndvaG5lcl9ibGtAZGF0YSAlPiUgc2V0X25hbWVzKHN0cnNwbGl0KCJCTEssMCwxLDIsMyw0LDUsNiw3LDgsOSwxMCwxMSxHZXNhbXQiLCAiLCIpW1sxXV0pLCBieT1jKCdpZCc9J0JMSycpKSAlPiUKICBtdXRhdGUoa2lkcz0oYDVgK2A2YCkvMiwga2lkcz1pZmVsc2Uoa2lkcyA9PSAwLCBOQSwga2lkcykpCgpkYXRhJHBhbmVsID0gIjUmNiIKCmRhdGEyID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUKICBsZWZ0X2pvaW4oCiAgICBlaW53b2huZXJfYmxrQGRhdGEgJT4lIHNldF9uYW1lcyhzdHJzcGxpdCgiQkxLLDAsMSwyLDMsNCw1LDYsNyw4LDksMTAsMTEsR2VzYW10IiwgIiwiKVtbMV1dKSwgYnk9YygnaWQnPSdCTEsnKSkgJT4lCiAgbXV0YXRlKGtpZHM9KGAxYCtgMmArYDNgK2A0YCtgNWArYDZgKS82LCBraWRzPWlmZWxzZShraWRzID09IDAsIE5BLCBraWRzKSkKCmRhdGEyJHBhbmVsID0gIjEtNiIKCmRhdGEgPSByYmluZChkYXRhLCBkYXRhMikKCmdnbWFwKG1hcCkgKwogIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9a2lkcyksIGRhdGE9ZGF0YSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpICsKICBmYWNldF93cmFwKH4gcGFuZWwpCmBgYAoKCiMjIyBCZXJlY2hudW5nIGRlciBQb3B1bGF0aW9uIGRlciBCbMO2Y2tlLCBTdHJ1a3R1cnF1b3RlCgpgYGB7cn0KU3RydWt0dXJxdW90ZSA9IDAuOQoKa2lkc19pbl9ibGtzID0gZWlud29obmVyX2Jsa0BkYXRhICU+JQogIHNldF9uYW1lcyhzdHJzcGxpdCgiQkxLLDAsMSwyLDMsNCw1LDYsNyw4LDksMTAsMTEsR2VzYW10IiwgIiwiKVtbMV1dKSAlPiUKICBtdXRhdGUoa2lkcz0oYDFgK2AyYCtgM2ArYDRgK2A1YCtgNmApLzYqU3RydWt0dXJxdW90ZSkgIyBGSVhNRSBvbmx5IDUgYW5kIDY/Cgpyb3cubmFtZXMoa2lkc19pbl9ibGtzKSA9IGtpZHNfaW5fYmxrcyRCTEsKYGBgCgojIyBWZXJmw7xnYmFyZSBQbMOkdHplCgpgYGB7cn0KcmVsZXZhbnRfa2FwYXMgPSBrYXBhcyAlPiUgc2VsZWN0KFNjaHVsbnVtbWVyLCBLYXBhPWBQbMOkdHplYCkgJT4lIGZpbHRlcihTY2h1bG51bW1lciAlaW4lIHJlbGV2YW50X3NjaG9vbHMpICU+JSBhcy5kYXRhLmZyYW1lKCkKYGBgCgojIyMgw5xiZXJwcsO8ZnVuZyBkZXIgU3VtbWUgZGVyIEthcGF6aXTDpHRlbiwgQW5tZWxkdW5nZW4gdW5kIEtpbmRlcnN0YXRpc3Rpa2VuCgpgYGB7cn0KJ1N1bW1lIEthcGFzJwpyZWxldmFudF9rYXBhcyAlPiUgLiRLYXBhICU+JSBzdW0KJ0FubWVsZHVuZ2VuJwprYXBhcyAlPiUgbXV0YXRlKEFubWVsZHVuZ2VuID0gYXMubnVtZXJpYyhnc3ViKCdbXjAtOV0nLCAnJywgQW5tZWxkdW5nZW4pKSkgJT4lIGZpbHRlcihTY2h1bG51bW1lciAlaW4lIHJlbGV2YW50X3NjaG9vbHMpICU+JSAuJEFubWVsZHVuZ2VuICU+JSBzdW0KJ0tpZHMgbGF1dCBTdGF0aXN0aWsnCmtpZHNfaW5fYmxrcyRraWRzICU+JSBzdW0KYGBgCgojIyBTY2h1bHdlZ2Ugdm9uIEJsw7Zja2VuIHp1IFNjaHVsZW4gYWdncmVnaWVyZW4KCkbDvHIgamVkZXMgV29obmdlYsOkdWRlIHN1Y2hlbiB3aXIgZGVuIHp1Z2Vow7ZyaWdlbiBCbG9jawoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBpbm5lcl9qb2luKHJvdXRlX21hdHJpeCAlPiUgZmlsdGVyKGRzdCAlaW4lIHJlbGV2YW50X3NjaG9vbHMpLCBieT1jKCdPSSc9J3NyYycpKQpoZWFkKHJvdXRlc19mcm9tX2Jsa3MpCmBgYAoKIyMjIFBsb3QgZGVyIHJlbGV2YW50ZW4gQmzDtmNrZSAobWl0IFdvaG5nZWLDpHVkZW4pIHVuZCBQb3B1bGF0aW9uCgpXZWxjaGUgQmzDtmNrZSBoYWJlbiBSb3V0ZW4oIHdlaWwgV29obmdlYsOkdWRlKSB1bmQgZWluZSBQb3B1bGF0aW9uICh3ZWlsIEtpbmRlciB6d2lzY2hlbiAxIHVuZCA2KT8KCmBgYHtyfQpwb2x5cyA9IHRpZHkoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nQkxLJykKcm91dGVkYXRhID0gcG9seXMgJT4lIGlubmVyX2pvaW4ocm91dGVzX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgc3VtbWFyaXNlKG49bigpKSwgYnk9YygnaWQnPSdCTEsnKSkKcG9wZGF0YSA9IHBvbHlzICU+JSBpbm5lcl9qb2luKGtpZHNfaW5fYmxrcyAlPiUgZmlsdGVyKGtpZHMgPiAwKSwgYnk9YygnaWQnPSdCTEsnKSkKCmdnbWFwKG1hcCkgKwogIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBmaWxsPSdibHVlJywgZGF0YT1wb3BkYXRhKSArCiAgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9cm91dGVkYXRhKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4ocm91dGVkYXRhJGxvbmcpLTAuMDEsIG1heChyb3V0ZWRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4ocm91dGVkYXRhJGxhdCktMC4wMSwgbWF4KHJvdXRlZGF0YSRsYXQpKzAuMDEpKQoKZ2dtYXAobWFwKSArCiAgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9cm91dGVkYXRhKSArCiAgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J2JsdWUnLCBkYXRhPXBvcGRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihyb3V0ZWRhdGEkbG9uZyktMC4wMSwgbWF4KHJvdXRlZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihyb3V0ZWRhdGEkbGF0KS0wLjAxLCBtYXgocm91dGVkYXRhJGxhdCkrMC4wMSkpCgpgYGAKCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyA9IHJvdXRlc19mcm9tX2Jsa3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZ3JvdXBfYnkoQkxLLCBkc3QpICU+JSBzdW1tYXJpc2UobWluPW1pbihkaXN0YW5jZSksIGF2Zz1tZWFuKGRpc3RhbmNlKSwgbWVkPW1lZGlhbihkaXN0YW5jZSksIG1heD1tYXgoZGlzdGFuY2UpKSAlPiUgdW5ncm91cAp0cmF2ZWxfZnJvbV9ibGtzCmBgYAoKIyMjIFBsb3QgZGVyIEJsw7Zja2UgbWl0IEbDpHJidW5nIG5hY2ggZHVyY2hzY2huaXR0bGljaGVtIFdlZyB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIyBTb3ppb8O2a29ub21pc2NoZSBEYXRlbgoKV2lyIGhhYmVuIFNvemlvw7Zrb25vbWlzY2hlIERhdGVuIGluIGRlbiBXYWhsYmV6aXJrZW4uIFN0cmF0ZWdpZToKLSBTY2huZWlkZW4gZGVyIFdhaGxiZXppcmtlIG1pdCBkZW4gQmzDtmNrZW4KLSDDnGJlcm5haG1lIGRlcyBQcm96ZW50d2VydGVzIHZvbSBXYWhsYmV6aXJrIGbDvHIgamVkZW4gKFVudGVyLSlibG9jawotIFp1b3JkbnVuZyB6dSBqZWRlbSBCbG9jayB1bmQgVmVyZWluaWd1bmcgZHVyY2ggRmzDpGNoZW4vV29obmhhdXMtZ2V3aWNodGV0ZXMgTWl0dGVsIGRlcyBQcm96ZW50d2VydGVzIAoKYGBge3J9ClVXQiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9VV0JfQUdIMjAxNi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKVVdCJElEID0gcGFzdGUwKFVXQiRCRVosIFVXQiRVV0IpCnNvemlvX1VXQiA9IHJlYWRfZXhjZWwoJ2Rvd25sb2FkL0RMX0JFX0FIMjAxNl9TdHJ1a3R1cmRhdGVuLnhsc3gnLCBzaGVldCA9IDMpICU+JSBzZWxlY3QoSUQsIHNnYklJdTY1PWBFaW53b2huZXIgdW50ZXIgNjUgaW4gU0dCIElJIDIwMTQgUHJvemVudGApClVXQiA9IFVXQiAlPiUgc3A6Om1lcmdlKHNvemlvX1VXQiwgYnk9J0lEJykKCmdncGxvdChicm9vbTo6dGlkeShVV0IsIHJlZ2lvbj0nSUQnKSAlPiUgaW5uZXJfam9pbihVV0IgJT4lIGFzLmRhdGEuZnJhbWUsIGJ5PWMoJ2lkJz0nSUQnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgpDaGVjayBmb3Igc2VsZiBpbnRlcnNlY3Rpb25zCmBgYHtyfQp3Z3M4NCA9IENSUyhwcm9qNHN0cmluZyhibGspKQplYV9wcm9qZWN0aW9uID0gQ1JTKCIrcHJvaj1sYWVhICtsYXRfMD01MiArbG9uXzA9MTAgK3hfMD00MzIxMDAwICt5XzA9MzIxMDAwMCArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSArbm9fZGVmcyIpCmJsa19pbl9iZXogPSBibGtbYmxrJEJFWiA9PSBiZXpfaWQsXQpnSXNWYWxpZChibGtfaW5fYmV6KQpibGtfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCBlYV9wcm9qZWN0aW9uKSwgYnlpZD1ULCB3aWR0aCA9IC0wLjEpCmFueSh4b3IoZ0ludGVyc2VjdHMoYmxrX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aChibGtfaW5fYmV6KSkgPT0gMSkpCnBsb3QoYmxrX2luX2JleikKI2dJbnRlcnNlY3RzKFVXQltVV0IkQkVaID09ICcwNycsXSwgYnlpZD1UUlVFKQpgYGAKCmBgYHtyfQp1d2JfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShVV0JbVVdCJEJFWiA9PSAnMDcnLF0sIGVhX3Byb2plY3Rpb24pLCBieWlkPVQsIHdpZHRoPS0wLjEpCmdJc1ZhbGlkKHV3Yl9pbl9iZXopCmFueSh4b3IoZ0ludGVyc2VjdHModXdiX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aCh1d2JfaW5fYmV6KSkgPT0gMSkpCnBsb3QodXdiX2luX2JleikKYGBgCgpgYGB7cn0KaW50ZXJzZWN0aW9uID0gZ0ludGVyc2VjdGlvbih1d2JfaW5fYmV6LCBibGtfaW5fYmV6LCBieWlkID0gVCwgZHJvcF9sb3dlcl90ZCA9IFQpCgpnSXNWYWxpZChpbnRlcnNlY3Rpb24pCnBsb3QoaW50ZXJzZWN0aW9uKQpgYGAKCmBgYHtyfQppbnRlcnNlY3Rpb25fdXdiX2RhdGEgPSBpbnRlcnNlY3Rpb24gJT4lIG92ZXIodXdiX2luX2JleikKaW50ZXJzZWN0aW9uX2Jsa19kYXRhID0gaW50ZXJzZWN0aW9uICU+JSBvdmVyKGJsa19pbl9iZXopCmludGVyc2VjdGlvbl9hcmVhID0gZ0FyZWEoaW50ZXJzZWN0aW9uLCBieWlkPVQpCmludGVyc2VjdGlvbl9kYXRhID0gY2JpbmQoCiAgICBpbnRlcnNlY3Rpb25fdXdiX2RhdGEgJT4lIHNlbGVjdChVV0JfSUQ9SUQsIHNnYklJdTY1KSwKICAgIGludGVyc2VjdGlvbl9ibGtfZGF0YSAlPiUgc2VsZWN0KEJMSywgQkxLX0Vpbnc9RWludyksCiAgICBkYXRhLmZyYW1lKGFyZWE9aW50ZXJzZWN0aW9uX2FyZWEpICMgRklYTUUgaG93IGVsc2UgdG8gbm9ybWFsaXplPwogICAgKQpgYGAKCmBgYHtyfQpsZW5ndGgoaW50ZXJzZWN0aW9uKQpucm93KGJsa19pbl9iZXopCiMgZGlkIHdlIG1pc3MgYW55IGJsb2Nrcz8Kc2V0ZGlmZihibGtfaW5fYmV6JEJMSywgaW50ZXJzZWN0aW9uX2RhdGEkQkxLKQpgYGAKClBybyBCbG9jayBtaXNjaGUgZGllIFNHQi1XZXJ0ZSBkZXIgVW50ZXJibMO2Y2tlIGdld2ljaHRldCBuYWNoIEZsw6RjaGUuCkZJWE1FIC0gYmVzc2VyIG5hY2ggQW56YWhsIGRlciBXb2huaMOkdXNlcj8KYGBge3J9CnNnYklJX2JsayA9IGludGVyc2VjdGlvbl9kYXRhICU+JQogIGdyb3VwX2J5KEJMSykgJT4lCiAgc3VtbWFyaXNlKHNnYklJdTY1PXN1bShzZ2JJSXU2NSphcmVhKS9zdW0oYXJlYSkvMTAwKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNnYklJX2JsaywgYnk9YygnaWQnPSdCTEsnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgojIyBBZGphemVueiB2b24gQmzDtmNrZW4KCmBgYHtyfQpyb3cubmFtZXMoYmxrX2luX2JleikgPSBibGtfaW5fYmV6JEJMSwpidWZmZWRlZF9ibGtfaW5fYmV6ID0gZ0J1ZmZlcihibGtfaW5fYmV6LCBieWlkPVQsIHdpZHRoPTQwKQphZGphY2VuY3kgPSBnSW50ZXJzZWN0cyhnQnVmZmVyKGJsa19pbl9iZXosIGJ5aWQ9VCwgd2lkdGg9NDApLCBieWlkID0gVCkKcm93bmFtZXMoYWRqYWNlbmN5KSA9IGJsa19pbl9iZXokQkxLCmNvbG5hbWVzKGFkamFjZW5jeSkgPSBibGtfaW5fYmV6JEJMSwoKYWRqYWNlbmN5X2RmID0gYWRqYWNlbmN5ICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSAlPiUKICBnYXRoZXIodG8sIGNvbm5lY3RlZCwgLWZyb20pICU+JSBmaWx0ZXIoY29ubmVjdGVkKSAlPiUKICBpbm5lcl9qb2luKHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSAlPiUgY29vcmRpbmF0ZXMoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByZW5hbWUoZnJvbV9sb25nPVYxLCBmcm9tX2xhdD1WMikgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSkgJT4lCiAgaW5uZXJfam9pbihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCkgJT4lIGNvb3JkaW5hdGVzKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcmVuYW1lKHRvX2xvbmc9VjEsIHRvX2xhdD1WMikgJT4lIG11dGF0ZSh0bz1yb3duYW1lcyguKSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J2dyYXknLCBkYXRhPWJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSkgKyAKICBnZW9tX3NlZ21lbnQoYWVzKHg9ZnJvbV9sb25nLCB5PWZyb21fbGF0LCB4ZW5kPXRvX2xvbmcsIHllbmQ9dG9fbGF0KSwgc2l6ZT0wLjEsIGNvbG9yPSdibGFjaycsIGRhdGE9YWRqYWNlbmN5X2RmKSArCiAgdGhlbWVfbm90aGluZygpICsgY29vcmRfbWFwKCkKCmdnc2F2ZSgnZmlncy9hZGphY2VuY3kucGRmJykKYWRqYWNlbmN5X2RmICU+JSBmaWx0ZXIoY29ubmVjdGVkICYgZnJvbSAhPSB0bykgJT4lIHNlbGVjdChmcm9tLCB0bykgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvYWRqYWNlbmN5LmNzdicpCmBgYAoKCiMjIFJlbGV2YW50ZSBEYXRlbiBhdXN3w6RobGVuCgpgYGB7cn0Kb3B0aW1fa2FwYXMgPSByZWxldmFudF9rYXBhcwpvcHRpbV9raWRzX2luX2Jsa3MgPSBraWRzX2luX2Jsa3MgJT4lIGZpbHRlcihraWRzID4gMCkgJT4lIGlubmVyX2pvaW4odHJhdmVsX21hdHJpeCwgYnk9J0JMSycpICU+JSBzZWxlY3QoQkxLLCBraWRzKQpucm93KG9wdGltX2tpZHNfaW5fYmxrcykKbnJvdyhvcHRpbV9rYXBhcykKCnNlbGVjdF9zY2hvb2xzID0gYXMuY2hhcmFjdGVyKG9wdGltX2thcGFzJFNjaHVsbnVtbWVyKQpzZWxlY3RfYmxrcyA9IGFzLmNoYXJhY3RlcihvcHRpbV9raWRzX2luX2Jsa3MkQkxLKQoKb3B0aW1fbWF0cml4ID0gaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIHRyYXZlbF9tYXRyaXgsIGJ5PSdCTEsnKVtzZWxlY3Rfc2Nob29sc10KCmRpbShvcHRpbV9tYXRyaXgpCgpvcHRpbV9rYXBhcyRLYXBhICU+JSBzdW0Kb3B0aW1fa2lkc19pbl9ibGtzJGtpZHMgJT4lIHN1bQpgYGAKCiMjIE5haXZlIChpbml0aWFsZSkgWnVvcmRudW5nOiBKZWRlciBCbG9jayB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CnNvbHV0aW9uID0gb3B0aW1fbWF0cml4ICU+JSBtdXRhdGUoQkxLPW9wdGltX2tpZHNfaW5fYmxrcyRCTEspICU+JSBnYXRoZXIoc2Nob29sLCBkaXN0LCAtQkxLKSAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWRpc3QpICU+JSB1bmdyb3VwCgpvcHRpbV9tYXRyaXggJT4lIHQgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWluKSkgJT4lIHN1bSgpCgpzb2xpbmVzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpICU+JSBpbm5lcl9qb2luKGNiaW5kKGFzLmRhdGEuZnJhbWUoY29vcmRpbmF0ZXMoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0pKSwgYmxrW2JsayRCRVogPT0gYmV6X2lkLF1AZGF0YVsnQkxLJ10pKQoKZGF0YSA9IHRpZHkoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nQkxLJykgJT4lIGxlZnRfam9pbihzb2x1dGlvbiwgYnk9YygnaWQnPSdCTEsnKSkKZ2dtYXAobWFwLCBkYXJrZW4gPSBjKDAuNSwgJ3doaXRlJykpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zY2hvb2wpLCBkYXRhPWRhdGEpICsKICBnZW9tX3NlZ21lbnQoYWVzKHg9VjEseT1WMix4ZW5kPWxvbix5ZW5kPWxhdCksIGRhdGE9c29saW5lcywgc2l6ZT0wLjMpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdibGFjaycsIHNpemU9MiwgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSkgKwogIGdlb21fcG9pbnQoYWVzKGxvbiwgbGF0LCBjb2xvcj1zcGF0aWFsX25hbWUpLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkgKwogIGd1aWRlcyhjb2xvcj1GLCBmaWxsPUYpCmBgYAoKIyMgRGFyc3RlbGx1bmcgZGVyIFp1b3JkbnVuZyBhbHMgVGFiZWxsZQoKYGBge3J9CmxpYnJhcnkoZm9ybWF0dGFibGUpCmBgYAoKYGBge3J9CnNvbHV0aW9uICU+JSBpbm5lcl9qb2luKG9wdGltX2tpZHNfaW5fYmxrcywgYnk9J0JMSycpICU+JSBpbm5lcl9qb2luKHRyYXZlbF9mcm9tX2Jsa3MsIGJ5PWMoJ0JMSyc9J0JMSycsICdzY2hvb2wnPSdkc3QnKSkgJT4lCiAgZ3JvdXBfYnkoc2Nob29sKSAlPiUgc3VtbWFyaXNlKAogICAga2lkcz1zdW0oa2lkcyksCiAgICBudW1fYmxvY2tzPW4oKSwKICAgIG1pbl9kaXN0PW1pbihtaW4pLAogICAgYXZnX2Rpc3Q9bWVhbigoa2lkcyphdmcpL3N1bShraWRzKSksCiAgICBtYXhfZGlzdD1tYXgobWF4KQogICkgJT4lCiAgaW5uZXJfam9pbihyZWxldmFudF9rYXBhcywgYnk9Yygnc2Nob29sJz0nU2NodWxudW1tZXInKSkgJT4lCiAgbXV0YXRlKAogICAgdXRpbGl6YXRpb249a2lkcy9LYXBhCiAgKSAlPiUgc2VsZWN0KAogICBTY2h1bGU9c2Nob29sLAogICBgQmzDtmNrZWA9bnVtX2Jsb2NrcywKICAgS2FwYXppdMOkdD1LYXBhLAogICBLaW5kZXI9a2lkcywKICAgQXVzbGFzdHVuZz11dGlsaXphdGlvbiwKICAgYFdlZyAobWluKWA9bWluX2Rpc3QsCiAgIGBXZWcgKMOYKWA9YXZnX2Rpc3QsCiAgIGBXZWcgKG1heClgPW1heF9kaXN0CiAgKSAlPiUKICBmb3JtYXR0YWJsZSgKICAgIGxpc3QoCiAgICAgIEtpbmRlciA9IGZvcm1hdHRlcigic3BhbiIsIHggfiBkaWdpdHMoeCwgMikpLAogICAgICBBdXNsYXN0dW5nID0gZm9ybWF0dGVyKCJzcGFuIiwKICAgICAgICBzdHlsZSA9IHggfiBzdHlsZShjb2xvciA9IGlmZWxzZSh4IDwgMSwgImdyZWVuIiwgInJlZCIpKSwKICAgICAgICB4IH4gaWNvbnRleHQoaWZlbHNlKHggPCAxLCAib2siLCAicmVtb3ZlIiksIHBlcmNlbnQoeCkpCiAgICAgICksCiAgICAgIGBXZWcgKMOYKWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIiksCiAgICAgIGBXZWcgKG1pbilgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpLAogICAgICBgV2VnIChtYXgpYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKQogICAgKQogICkKYGBgCgojIyBuZEggdW5kIExNQi1EYXRlbiAobmljaHQgb2ZmZW4pCgpgYGB7cn0KbmRoX2xtYiA9IHJlYWRfZXhjZWwoJ2Rvd25sb2FkL0xNQl9uZEgyMDE1Lnhsc3gnKSAlPiUgc2VsZWN0KAogIGVudGl0eV9pZD1CU04sCiAgbmRIPWBBbnRlaWwgbmRIYCwKICBMTUI9YEFudGVpbCBMTUJgCikKbmRoX2xtYgpgYGAKCgojIyBEYXRlbiBmw7xyIGRpZSBBcHAgc3BlaWNoZXJuCgotIGVudGl0aWVzLmdlb2pzb24KLSBlbnRpdGllcy5jc3YKLSB1bml0cy5nZW9qc29uCi0gdW5pdHMuY3N2Ci0gd2VpZ2h0cy5jc3YKLSBhc3NpZ25tZW50LmNzdgoKIyMjIE5ldWUgRGF0ZW4KCgojIyMjIEVudGl0aWVzIC8gU2NodWxlbgoKYGBge3J9CmVudGl0aWVzID0gc3Vic2V0KHJlX3NjaHVsc3RhbmRfZGYsIHNwYXRpYWxfbmFtZSAlaW4lIHJlbGV2YW50X3NjaG9vbHMpICU+JQogIHJlbmFtZShlbnRpdHlfaWQgPSBzcGF0aWFsX25hbWUpICU+JQogIHNlbGVjdCgtZ21sX2lkLCAtc3BhdGlhbF9hbGlhcywgLXNwYXRpYWxfdHlwZSkgJT4lCiAgaW5uZXJfam9pbihyZW5hbWUocmVsZXZhbnRfa2FwYXMsIGNhcGFjaXR5PUthcGEpLCBieT1jKCdlbnRpdHlfaWQnPSdTY2h1bG51bW1lcicpKSAlPiUKICBsZWZ0X2pvaW4obmRoX2xtYiwgYnk9YygnZW50aXR5X2lkJykpCmNvb3JkaW5hdGVzKGVudGl0aWVzKSA9IH4gbG9uICsgbGF0CnByb2o0c3RyaW5nKGVudGl0aWVzKSA9IENSUygiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0ICtub19kZWZzIikKZmlsZS5yZW1vdmUoJ2FwcC9kYXRhL2VudGl0aWVzLmdlb2pzb24nKQplbnRpdGllcyAlPiUgd3JpdGVPR1IoJ2FwcC9kYXRhL2VudGl0aWVzLmdlb2pzb24nLCBsYXllcj0iZW50aXRpZXMiLCBkcml2ZXI9Ikdlb0pTT04iLCBjaGVja19leGlzdHM9RikKZW50aXRpZXNAZGF0YSAlPiUgc2VsZWN0KGVudGl0eV9pZCwgY2FwYWNpdHkpICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL2VudGl0aWVzLmNzdicpCmBgYAoKIyMjIyBVbml0cyAvIFN0YXRpc3Rpc2NoZSBCbMO2Y2tlCgpgYGB7cn0KdW5pdHMgPSBzdWJzZXQoYmxrLCBCRVogPT0gYmV6X2lkKQoKdW5pdHNAZGF0YSRQTFIgPSBOVUxMCiN1bml0c0BkYXRhJEVpbncgPSBOVUxMCnVuaXRzQGRhdGEkQkVaID0gTlVMTAp1bml0c0BkYXRhJHVuaXRfaWQgPSB1bml0c0BkYXRhJEJMSwp1bml0c0BkYXRhJEJMSyA9IE5VTEwKCnVuaXRzID0gdW5pdHMgJT4lIHNwOjptZXJnZShzZ2JJSV9ibGsgJT4lIHJlbmFtZSh1bml0X2lkPUJMSykpICU+JQogIHNwOjptZXJnZShvcHRpbV9raWRzX2luX2Jsa3MgJT4lIHNlbGVjdCh1bml0X2lkPUJMSywgcG9wdWxhdGlvbj1raWRzKSkKCmZpbGUucmVtb3ZlKCdhcHAvZGF0YS91bml0cy5nZW9qc29uJykKdW5pdHMgJT4lIHdyaXRlT0dSKCdhcHAvZGF0YS91bml0cy5nZW9qc29uJywgbGF5ZXI9ImVudGl0aWVzIiwgZHJpdmVyPSJHZW9KU09OIiwgY2hlY2tfZXhpc3RzPUYpCnVuaXRzQGRhdGEgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvdW5pdHMuY3N2JykKYGBgCgojIyMjIFp1b3JkbnVuZwoKYGBge3J9CmFzc2lnbm1lbnQgPSBzb2x1dGlvbiAlPiUgc2VsZWN0KHVuaXRfaWQ9QkxLLCBlbnRpdHlfaWQ9c2Nob29sKQphc3NpZ25tZW50ICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL2Fzc2lnbm1lbnQuY3N2JykKYGBgCgojIyMjIFdlaWdodHMgLyBTY2h1bHdlZ2UKCmBgYHtyfQp0cmF2ZWxfZnJvbV9ibGtzICU+JQogIHJlbmFtZSh1bml0X2lkPUJMSywgZW50aXR5X2lkPWRzdCkgJT4lCiAgI2dhdGhlcih3ZWlnaHQsIHZhbHVlLCAtdW5pdF9pZCwgLWVudGl0eV9pZCkgJT4lCiAgd3JpdGVfY3N2KCdhcHAvZGF0YS93ZWlnaHRzLmNzdicpCmBgYAoKIyMjIyBadXPDpHR6bGljaGUgRGF0ZW4KCmBgYHtyfQpmaWxlLmNvcHkoJ2Rvd25sb2FkL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJywgJ2FwcC9kYXRhL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJykKYGBgCgo=